package com.cymal.condition.subject;

import com.cymal.cache.BaseSubjectCache;
import com.cymal.cache.ChooseLocalCache;
import com.cymal.cache.ChooseRedisCache;
import com.cymal.condition.Condition;
import com.cymal.entity.po.answer.Choose;
import com.cymal.entity.po.subject.AbstractSubject;
import com.cymal.entity.po.subject.ChooseSubject;
import com.cymal.service.ChooseDbService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Semaphore;

/**
 * 选择题匹配器
 */
@Component
@Slf4j
public class ChooseCondition implements Condition<ChooseSubject, Choose> {


    /**
     * 空值, 防止缓存击穿.
     */
    private static final List<Choose> CHOOSES_EMPTY = null;


    /**
     * 最大并发数
     */
    private static final int MAX_CONCURRENCY_NUM = 5;

    /**
     * 一级缓存
     */
    @Autowired
    private ChooseLocalCache localCache;


    /**
     * 二级缓存
     */
    @Autowired
    private ChooseRedisCache redisCache;


    /**
     * db
     */
    @Autowired
    private ChooseDbService dbData;


    /**
     * 控制最大查库并发数
     */
    private Semaphore lock = new Semaphore(MAX_CONCURRENCY_NUM);


    /**
     * 匹配.
     *
     * @param subject  题目.
     * @param requests 请求.
     * @return true or false.
     */
    @Override
    public Boolean matched(ChooseSubject subject, Choose... requests) {
        if (ArrayUtils.isNotEmpty(requests)) {
            var key = new BaseSubjectCache.Key(subject.getSubjectType(), subject.getId());
            var chooses = getChooses(key);
            Optional<Boolean> result = Arrays.stream(requests)
                    .map(request -> chooses.contains(request))
                    .filter(BooleanUtils::isFalse)
                    .findFirst();
            return result.orElse(Boolean.TRUE);
        } else {
            return Boolean.FALSE;
        }
    }


    /**
     * 获取chooses.
     * 顺序：一级缓存 -> 布隆过滤器 -> 二级缓存 -> database.
     *
     * @param key key
     * @return List<Choose>
     */
    private List<Choose> getChooses(BaseSubjectCache.Key key) {
        var chooses = localCache.get(key);
        if (CollectionUtils.isEmpty(chooses)) {
            chooses = redisCache.get(key);
            if (CollectionUtils.isEmpty(chooses)) {
                try {
                    lock.acquire();
                    chooses = dbData.get(key);
                    if (CollectionUtils.isEmpty(chooses)) {
                        redisCache.put(key, CHOOSES_EMPTY);
                        log.error("缓存穿透, 存在恶意流量: [{}]", key);
                    } else {
                        redisCache.put(key, chooses);
                    }
                } catch (Exception e) {
                    log.error("超过最大查库限制", e);
                } finally {
                    lock.release();
                }
            }
        }
        return chooses;
    }


    /**
     * 匹配
     *
     * @param subject  题目.
     * @param requests 请求.
     * @return ture or false.
     */
    @Override
    public Boolean matched(ChooseSubject subject, Collection<Choose> requests) {
        return matched(subject, requests.toArray(new Choose[requests.size()]));
    }


    /**
     * 返回匹配类型
     *
     * @return 匹配类型
     */
    @Override
    public AbstractSubject.SubjectType conditionType() {
        return AbstractSubject.SubjectType.choose;
    }


}
